Skip to content

feat(language-server): lazy document state via ensureCurrent + pull diagnostics#887

Draft
SevInf wants to merge 5 commits into
mainfrom
lsp-document-state
Draft

feat(language-server): lazy document state via ensureCurrent + pull diagnostics#887
SevInf wants to merge 5 commits into
mainfrom
lsp-document-state

Conversation

@SevInf

@SevInf SevInf commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Reworks the language server's document-state lifecycle from eager, defensive reparse to invalidate-on-change + lazily materialize-on-read, synchronously, and moves diagnostics from push to LSP 3.17 pull (textDocument/diagnostic). Implements the lsp-document-state spec committed as this PR's first commit (projects/lsp-document-state/).

didChange  ──▶  version bump only (free from TextDocuments; no parse)
read req   ──▶  await project load (only while uncached; config eval is async)
            ──▶  ensureCurrent(project, uri)   (SYNC: reparse iff version changed)
            ──▶  derive (completion / diagnostics / tokens / folding)

Changes

  • Lazy lifecycle (src/project-artifacts.ts, src/server.ts): ensureCurrent(project, uri) is the single synchronous materialize-on-read seam. CachedDocument carries the last-parsed version; a read at an unchanged version does zero parsing, and an edit-then-immediate-read costs exactly one parse. currentDocumentArtifact — the per-request whole-document text compare + defensive reparse — is deleted; completion, semantic tokens, and folding all read through the one per-project cache. Config reloads invalidate() cached versions so the fast path stays sound across prisma-next.config.ts changes. TextDocuments is retained as the text mirror / lifecycle / version source.
  • Pull diagnostics with push fallback (src/server.ts): when the client advertises textDocument.diagnostic, the server advertises diagnosticProvider and serves full reports through ensureCurrent; didOpen/didChange become invalidate-only (no eager publish), and config/watched-file changes send workspace/diagnostic/refresh (gated on refreshSupport) instead of republishing. Clients without pull support keep the previous eager push behavior unchanged. Exactly one transport is ever active per client — structurally, since every push site is gated on the same capability flag. Flags ship as { interFileDependencies: false, workspaceDiagnostics: false } with a scope comment: this reflects the current single-input implementation, not a property of PSL, and flips with the future multi-input symbol table.
  • Report builder seam: buildDocumentDiagnosticReport(project, uri) is project-scoped so the future multi-file table can attach relatedDocuments additively — no rewrite needed.

Why

  • Every edit previously parsed twice (eager publish + currentDocumentArtifact's per-request reparse with an O(n) whole-text compare) to dodge a stale-buffer race the LSP spec's didChange ordering guarantee already rules out. Freshness is now keyed on the monotonic version that TextDocuments maintains anyway.
  • The server is single-threaded: there is no await between reading the buffer and deriving from it, so a derived result is always internally consistent for one version — tsserver's version-keyed lazy recompute fits; rust-analyzer-style snapshots/cancellation are unnecessary. Async stays confined to project/config resolution (executable prisma-next.config.ts, cached and de-duped).
  • Dropping eager compute on change is only safe once diagnostics are pull-served, which is why lifecycle and transport land together as one slice.

Validation

  • Package gates: pnpm --filter @prisma-next/language-server test (187 tests, incl. no-reparse-at-unchanged-version, one-parse edit-then-complete, pull/push capability gating, refresh semantics), typecheck, lint; playground typecheck/lint.
  • Workspace: pnpm build, pnpm typecheck, pnpm lint:deps clean. (pnpm test:packages locally red only in mongo-runtime/CLI tests that fail identically on main — mongod-less machine.)
  • Manual QA: headless end-to-end run over the real playground WebSocket bridge (pull report with parse error, zero publishDiagnostics to the pull client, post-edit pull correct, tokens/folding healthy) — script + report under projects/lsp-document-state/manual-qa*. Visual Monaco-marker check (Part B) pending a browser run.

Non-goals

Removing TextDocuments, the multi-input project-wide symbol table, multi-project membership, interFileDependencies: true / workspace/diagnostic, and any behavioral change to completion/semantic-tokens/folding beyond their data source. See projects/lsp-document-state/spec.md § Non-goals.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: fe27255d-760b-4e45-bce1-aaf642a72cb8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch lsp-document-state

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@SevInf SevInf force-pushed the lsp-autocomplete branch from ee9c443 to 82b8f4e Compare July 3, 2026 09:29
Base automatically changed from lsp-autocomplete to main July 3, 2026 15:06
SevInf added 5 commits July 3, 2026 16:02
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…eCurrent seam

Key the per-project document cache on TextDocument.version: update() skips
recompute at an unchanged version, ensureCurrent(project, uri) is the single
materialize-on-read seam for completion, semantic tokens, and folding, and
the per-request whole-text compare (currentDocumentArtifact) is gone.
Config reloads invalidate cached versions so the next read recomputes
against the new stack. Push diagnostics on didChange are unchanged.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Advertise diagnosticProvider ({ interFileDependencies: false,
workspaceDiagnostics: false } — single-input implementation scope, to flip
with the future multi-input symbol table) when the client supports
textDocument/diagnostic, and serve full reports through the ensureCurrent
seam via a project-scoped report builder that can carry relatedDocuments
later. For pull clients didOpen/didChange become invalidate-only and config
changes request workspace/diagnostic/refresh (gated on refreshSupport)
instead of republishing; push clients keep the previous eager publish
behavior. Exactly one transport is ever active per client.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Section 2 still claimed a reparse on every change, contradicting the
pull-diagnostics description: parsing happens at most once per document
version, when a read triggers materialization.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…n report, and trace

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
@SevInf SevInf force-pushed the lsp-document-state branch from bc000fb to 2c5a078 Compare July 6, 2026 08:58
@SevInf SevInf changed the title docs(language-server): spec for lazy document state + pull diagnostics feat(language-server): lazy document state via ensureCurrent + pull diagnostics Jul 6, 2026
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 160.74 KB (0%)
postgres / emit 147.87 KB (0%)
mongo / no-emit 98.2 KB (0%)
mongo / emit 89.39 KB (0%)
cf-worker / no-emit 188.88 KB (0%)
cf-worker / emit 174.17 KB (0%)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant